package luccy.desc.impl;

import static luccy.Constants.EMPTY_ARGS;

import java.lang.reflect.Constructor;
import java.util.ArrayList;
import java.util.List;

import luccy.desc.ClassDesc;
import luccy.desc.ConstructorDesc;
import luccy.util.AssertionUtil;
import luccy.util.ClassUtil;

public class ConstructorDescImpl implements ConstructorDesc {

	private ClassDesc classDesc = null;

	private List<ConstructorInfo> constructorInfoList = new ArrayList<ConstructorInfo>();

	public ConstructorDescImpl(final ClassDesc classDesc) {
		AssertionUtil.assertNotNull(classDesc);
		this.classDesc = classDesc;
		final Class componentClass = classDesc.getComponentClass();
		final Constructor[] constructors = componentClass.getConstructors();
		analyzeConstructors(constructors);
	}

	protected void analyzeConstructors(final Constructor[] constructors) {
		AssertionUtil.assertNotNull(constructors);
		for (Constructor c : constructors) {
			final Class[] paramTypes = c.getParameterTypes();
			final int paramLength = paramTypes.length;
			final boolean[] primitives = new boolean[paramLength];
			boolean allNotPrimitives = true;
			for (int i = 0; i < primitives.length; i++) {
				final boolean prim = paramTypes[i].isPrimitive();
				primitives[i] = prim;
				if (prim) {
					allNotPrimitives = false;
				}
			}
			ConstructorInfo info = new ConstructorInfo(c, paramTypes,
					paramLength, primitives, allNotPrimitives);
			constructorInfoList.add(info);
		}
	}

	public final ClassDesc getClassDesc() {
		return classDesc;
	}

	public Constructor getSuitableConstructor(Object... args) {
		if (args == null) {
			args = EMPTY_ARGS;
		}
		outer: for (int i = 0; i < constructorInfoList.size(); i++) {
			ConstructorInfo info = constructorInfoList.get(i);
			if (info.getParamLength() != args.length) {
				continue;
			}
			for (int j = 0; j < args.length; j++) {
				if (args[j] == null) {
					continue;
				}
				final Class<? extends Object> argsClass = args[j].getClass();
				if (ClassUtil.isAssignableFrom(argsClass, info.getParamType(j))) {
					continue;
				}
				continue outer;
			}
			return info.getConstructor();
		}
		return null;
	}

	private static class ConstructorInfo {

		private final Constructor constructor;

		private final Class[] paramTypes;

		private final int paramLength;

		private final boolean[] primitives;

		public ConstructorInfo(final Constructor constructor,
				final Class[] paramTypes, int paramLength,
				boolean[] primitives, boolean allNotPrimitives) {
			this.constructor = constructor;
			this.paramTypes = paramTypes;
			this.paramLength = paramLength;
			this.primitives = primitives;
		}

		public Constructor getConstructor() {
			return constructor;
		}

		public int getParamLength() {
			return paramLength;
		}

		public Class[] getParamTypes() {
			return paramTypes;
		}

		public boolean[] getPrimitives() {
			return primitives;
		}

		public Class getParamType(int index) {
			if (index > getParamLength()) {
				return null;
			}
			return paramTypes[index];
		}

		public boolean isPrimitive(int index) {
			if (index > getParamLength()) {
				return false;
			}
			return primitives[index];
		}

	}
}
